If your application requires it, you can customize the user interface for identifying files. To customize a dialog box, you should
Depending on the level of customizing you require in your dialog box, you may need to write as many as four different callback routines:
To provide the interface illustrated in Figure 3-7 , for example, you could replace the definition of DoOpenCmd given earlier in Listing 3-1 by the definition given in Listing 3-3 .
In addition to the information passed to StandardGetFile , CustomGetFile requires the resource ID of the customized dialog box, the location of the dialog box on the screen, and pointers to any callback routines and private data you are using.
Listing 3 Presenting a customized Open dialog box
FUNCTION DoOpenCmd: OSErr;
VAR
myReply: StandardFileReply; {Standard File reply record}
myTypes: SFTypeList; {types of files to display}
myPoint: Point; {upper-left corner of box}
myErr: OSErr;
CONST
kCustomGetDialog = 4000; {resource ID of custom dialog}
BEGIN
myErr := noErr;
SetPt(myPoint, -1, -1); {center the dialog}
myTypes[0] := 'SRFD'; {SurfDraw files}
myTypes[1] := 'STUP'; {startup screens}
myTypes[2] := 'PICT'; {picture files}
myTypes[3] := 'RTFT'; {rich text format}
CustomGetFile(@MyCustomFileFilter, 4, myTypes, myReply,
kCustomGetDialog, myPoint, @MyDlgHook,
NIL, NIL, NIL, NIL);
IF myReply.sfGood THEN
myErr := DoOpenFile(myReply.sfFile);
DoOpenCmd := myErr;
END;
In Listing 3-3 , CustomGetFile is passed two callback routines, a file filter function (MyCustomFileFilter) and a dialog hook function (MyDlgHook). See Listing 3-8 ( A sample file filter function ) and Listing 3-9 ( A sample dialog hook function ) for sample definitions of these functions.
You can also supply data of your own to the callback routines through a new parameter, yourDataPtr , which you pass to CustomGetFile and CustomPutFile .
To describe a dialog box, you supply a 'DLOG' resource that defines the box itself and a 'DITL' resource that defines the items in the dialog box.
Listing 3-4 shows the resource definition of the default Open dialog box, in Rez input format. (Rez is the resource compiler provided with Apple's Macintosh Programmer's Workshop [MPW]. For a description of Rez format, see the manual that accompanies the MPW software, MPW: Macintosh Programmer's Development Environment.)
Listing 4 The definition of the default Open dialog box
resource 'DLOG' (-6042, purgeable)
{
{0, 0, 166, 344}, dBoxProc, invisible, noGoAway, 0,
-6042, "", noAutoCenter
};
Listing 3-5 shows the resource definition of the default Save dialog box, in Rez input format.
Listing 5 The definition of the default Save dialog box
resource 'DLOG' (-6043, purgeable)
{
{0, 0, 188, 344}, dBoxProc, invisible, noGoAway, 0,
-6043, "", noAutoCenter
};
You can also use the stand-alone resource editor ResEdit, available from Apple Computer, Inc., or other resource-editing utilities available from third-party developers to create customized dialog box and dialog item list resources.
You must provide an item list (in a 'DITL' resource with the ID specified in the 'DLOG' resource) for each dialog box you define. Add new items to the end of the default lists. CustomGetFile expects the first 9 items in a customized dialog box to have the same functions as the corresponding items in the StandardGetFile dialog box; CustomPutFile expects the first 12 items to have the same functions as the corresponding items in the StandardPutFile dialog box. If you want to eliminate one of the standard items from the display, leave it in the item list but place its coordinates outside the bounds of the dialog box rectangle.
Listing 3-6 shows the dialog item list for the default Open dialog box, in Rez input format. See "Writing a Dialog Hook Function" beginning on Writing a Dialog Hook Function for a list of the dialog box elements these items represent.
Listing 6 The item list for the default Open dialog box
resource 'DITL'(-6042)
{ {
{135, 252, 155, 332}, Button { enabled, "Open" },
{104, 252, 124, 332}, Button { enabled, "Cancel" },
{0, 0, 0, 0}, HelpItem { disabled, HMScanhdlg {-6042}},
{8, 235, 24, 337}, UserItem { enabled },
{32, 252, 52, 332}, Button { enabled, "Eject" },
{60, 252, 80, 332}, Button { enabled, "Desktop" },
{29, 12, 159, 230}, UserItem { enabled },
{6, 12, 25, 230}, UserItem { enabled },
{91, 251, 92, 333}, Picture { disabled, 11 },
} };
Listing 3-7 shows the dialog item list for the default Save dialog box, in Rez input format.
Listing 7 The item list for the default Save dialog box
resource 'DITL'(-6043)
{ {
{161, 252, 181, 332}, Button { enabled, "Save" },
{130, 252, 150, 332}, Button { enabled, "Cancel" },
{0, 0, 0, 0}, HelpItem { disabled, HMScanhdlg {-6043}},
{8, 235, 24, 337}, UserItem { enabled },
{32, 252, 52, 332}, Button { enabled, "Eject" },
{60, 252, 80, 332}, Button { enabled, "Desktop" },
{29, 12, 127, 230}, UserItem { enabled },
{6, 12, 25, 230}, UserItem { enabled },
{119, 250, 120, 334}, Picture { disabled, 11 },
{157, 15, 173, 227}, EditText { enabled, "" },
{136, 15, 152, 227}, StaticText { disabled, "Save as:" },
{88, 252, 108, 332}, UserItem { disabled },
} };
The third item in each list ( HelpItem ) supplies Apple's Balloon Help for items in the dialog box. This third item specifies the resource ID of the 'hdlg' resource that contains the help strings for the standard dialog items. If you want to modify the help text of an existing dialog item, you should copy the original 'hdlg' resource from the System file into your application's resource fork and modify the text in the copied resource as desired; then you must change the resource ID specified in HelpItem to the resource ID of the copied and modified resource. To provide Balloon Help for your own items, supply a second 'hdlg' resource and reference it with another help item at the end of the list. The existing items retain their default text (unless you change that text, as described).
The default dialog item lists used by the original Standard File Package routines do not contain help items, but the Standard File Package does provide Balloon Help when you call those routines in system software version 7.0 and later. If you call one of the original routines and the specified dialog item list does not contain any help items, the Standard File Package uses its default help text for the standard dialog items. If, however, the dialog item list does contain a help item, the Standard File Package assumes that your application provides the text for all help items, including the standard dialog items.
The default Standard File Package dialog boxes support color. The System file contains 'dctb' resources with the same resource IDs as the default dialog boxes, so that the Dialog Manager uses color graphics ports for the default dialog boxes. (See the chapter "Dialog Manager" of Inside Macintosh: Macintosh Toolbox Essentials for a description of the 'dctb' resource.) If you create your own dialog boxes, include 'dctb' resources.
A file filter function determines which files appear in the displayed list when the user is opening a file. Both StandardGetFile and CustomGetFile recognize file filter functions.
When the Standard File Package is displaying the contents of a volume or folder, it checks the file type of each file and filters out files whose types do not match your application's specifications. (Your application can specify which file types are to be displayed through the typeList parameter to either StandardGetFile or CustomGetFile , as described in "Presenting the Standard User Interface," .) If your application also supplies a file filter function, the Standard File Package calls that function each time it identifies a file of an acceptable type.
The file filter function receives a pointer to the file's catalog information record (described in the chapter "File Manager" in this book). The function evaluates the catalog entry and returns a Boolean value that determines whether the file is filtered (that is, a value of TRUE suppresses display of the filename, and a value of FALSE allows the display). If you do not supply a file filter function, the Standard File Package displays all files of the specified types.
A file filter function to be called by StandardGetFile must use this syntax:
FUNCTION MyStandardFileFilter (pb: CInfoPBPtr): Boolean;
The single parameter passed to your standard file filter function is the address of a catalog information parameter block; see the chapter "File Manager" in this book for a description of the fields of that parameter block.
When CustomGetFile calls your file filter function, it can also receive a pointer to any data that you passed in through the call to CustomGetFile . A file filter function to be called by CustomGetFile must use this syntax:
FUNCTION MyCustomFileFilter (pb: CInfoPBPtr; myDataPtr: Ptr):
Boolean;
Listing 3-8 shows a sample file filter function to be called by CustomGetFile . You might define a file filter function like this to support the custom dialog box illustrated in Figure 3-7 , which lists files of the type shown in the pop-up box.
Listing 8 A sample file filter function
FUNCTION MyCustomFileFilter (pb: CInfoPBPtr; myDataPtr: Ptr): Boolean;
BEGIN
MyCustomFileFilter := TRUE; {default: don't show the file}
IF pb^.ioFlFndrInfo.fdType = gTypesArray[gCurrentType] THEN
MyCustomFileFilter := FALSE; {show the file}
END;
In Listing 3-8 , the application global variable gCurrentType contains the index in the array gTypesArray of the currently selected file type. If the type of a file passed in for evaluation matches the current file type, the filter returns FALSE , indicating that StandardGetFile should put it in the list. See Listing 3-9 ( A sample dialog hook function ) for an example of how you can use a dialog hook function to change the value of gCurrentType according to user selections in the pop-up menu control.
A dialog hook function handles item selections in a dialog box. It receives a pointer to the dialog record and an item number from the ModalDialog procedure via the Standard File Package each time the user selects one of the dialog items. Your dialog hook function checks the item number of each selected item, and then either handles the selection or passes it back to the Standard File Package.
If you provide a dialog hook function, CustomPutFile and CustomGetFile call your function immediately after calling ModalDialog . They pass your function the item number returned by ModalDialog , a pointer to the dialog record, and a pointer to the data received from your application, if any. The dialog hook function must use this syntax:
FUNCTION MyDlgHook (item: Integer; theDialog: DialogPtr;
myDataPtr: Ptr): Integer;
Your dialog hook function returns as its function result an integer that is either the item number passed to it or some other item number. If it returns one of the item numbers in the following list of constants, the Standard File Package handles the selected item as described later in this section. If your dialog hook function does not handle a selection, it should pass the item number back to the Standard File Package for processing by setting its return value equal to the item number.
CONST {items that appear in both the Open and Save dialog boxes}
sfItemOpenButton = 1; {Save or Open button}
sfItemCancelButton = 2; {Cancel button}
sfItemBalloonHelp = 3; {Balloon Help}
sfItemVolumeUser = 4; {volume icon and name}
sfItemEjectButton = 5; {Eject button}
sfItemDesktopButton = 6; {Desktop button}
sfItemFileListUser = 7; {display list}
sfItemPopUpMenuUser = 8; {directory pop-up menu}
sfItemDividerLinePict = 9; {dividing line between buttons}
{items that appear in Save dialog boxes only}
sfItemFileNameTextEdit = 10; {filename field}
sfItemPromptStaticText = 11; {filename prompt text area}
sfItemNewFolderUser = 12; {New Folder button}
You must write your own dialog hook function to handle any items you have added to the dialog box.
The constants that represent disabled items ( sfItemBalloonHelp , sfItemDividerLinePict , and sfItemPromptStaticText ) have no effect, but they are defined in the header files for the sake of completeness.
The Standard File Package also recognizes a number of constants that do not represent any actual item in the dialog list; these constants are known as pseudo-items. There are two kinds of pseudo-items:
The sfHookFirstCall constant is an example of the first kind of pseudo-item. The Standard File Package sends this pseudo-item to your dialog hook function immediately before it displays the dialog box. Your function typically reacts to this item number by performing any necessary initialization.
You can pass back other pseudo-items to indicate that you've handled the user selection or to request some action by the Standard File Package. For example, if the list of files and folders must be rebuilt because of a user selection, you can pass back the pseudo-item sfHookRebuildList. Similarly, when your application handles the selection and needs no further action by the Standard File Package, it should return sfHookNullEvent . When the dialog hook function passes either sfHookNullEvent or an item number that the Standard File Package doesn't recognize, it does nothing.
The Standard File Package recognizes these pseudo-item numbers:
CONST {pseudo-items available prior to version 7.0}
sfHookFirstCall = -1; {initialize display}
sfHookCharOffset = $1000; {offset for character input}
sfHookNullEvent = 100; {null event}
sfHookRebuildList = 101; {redisplay list}
sfHookFolderPopUp = 102; {display parent-directory menu}
sfHookOpenFolder = 103; {display contents of }
{ selected folder or volume}
{additional pseudo-items introduced in version 7.0}
sfHookLastCall = -2; {clean up after display}
sfHookOpenAlias = 104; {resolve alias}
sfHookGoToDesktop = 105; {display contents of desktop}
sfHookGoToAliasTarget = 106; {select target of alias}
sfHookGoToParent = 107; {display contents of parent}
sfHookGoToNextDrive = 108; {display contents of next drive}
sfHookGoToPrevDrive = 109; {display contents of previous drive}
sfHookChangeSelection = 110; {select target of reply record}
sfHookSetActiveOffset = 200; {switch active item}
The Standard File Package uses a set of modal-dialog filter functions (described in "Writing a Modal-Dialog Filter Function" ) to map user actions during the dialog onto the defined item numbers. Some of the mapping is indirect. A click of the Open button, for example, is mapped to sfItemOpenButton only if a file is selected in the display list. If a folder or volume is selected, the Standard File Package maps the selection onto the pseudo-item sfHookOpenFolder .
The lists that follow summarize when various items and pseudo-items are generated and how they are handled. The descriptions indicate the simplest mouse action that generates each item; many of the items can also be generated by keyboard actions, as described in "Keyboard Equivalents" .
Any indicated effects of passing back these constants do not occur until the Standard File Package receives the constant back from your dialog hook function.
The pseudo-items are messages that allow your application and the Standard File Package to communicate and support various features added since the original design of the Standard File Package.
The Standard File Package generates three pseudo-items that give your application the chance to control a customized display.
Your application can generate three pseudo-items to request services from the Standard File Package.
The Standard File Package's own modal-dialog filter functions generate a number of pseudo-items that allow its dialog hook functions to support various features introduced since the original design of the standard file dialog boxes. Except under extraordinary circumstances, your dialog hook function always passes any of these item numbers back to the Standard File Package for processing.
The CustomGetFile and CustomPutFile procedures call your dialog hook function for item selections in both the main dialog box and any subsidiary dialog boxes (such as the dialog box for naming a new folder while saving a document through CustomPutFile ). To determine whether the dialog record describes the main dialog box or a subsidiary dialog box, check the value of the refCon field in the window record in the dialog record.
Prior to system software version 7.0, the Standard File Package did not call your dialog hook function during subsidiary dialog boxes. Dialog hook functions for the new CustomGetFile and CustomPutFile procedures must check the dialog window's refCon field to determine the target of the dialog record.
The defined values for the refCon field represent the Standard File dialog boxes.
CONST
sfMainDialogRefCon = 'stdf'; {main dialog box}
sfNewFolderDialogRefCon = 'nfdr'; {New Folder dialog box}
sfReplaceDialogRefCon = 'rplc'; {name conflict dialog box}
sfStatWarnDialogRefCon = 'stat'; {stationery warning}
sfErrorDialogRefCon = 'err '; {general error report}
sfLockWarnDialogRefCon = 'lock'; {software lock warning}
sfMainDialogRefCon The main dialog box, either Open or Save.
sfNewFolderDialogRefCon The New Folder dialog box.
sfReplaceDialogRefCon The dialog box requesting verification for replacing a file of the same name.
sfStatWarnDialogRefCon The dialog box warning that the user is opening the master copy of a stationery pad, not a piece of stationery.
sfErrorDialogRefCon A dialog box reporting a general error.
sfLockWarnDialogRefCon The dialog box warning that the user is opening a locked file and won't be allowed to save any changes.
Listing 3-9 defines a dialog hook function that handles user selections in the customized Open dialog box illustrated in Figure 3-7 . Note that this dialog hook function handles selections only in the main dialog box, not in any subsidiary dialog boxes.
Listing 9 A sample dialog hook function
FUNCTION MyDlgHook (item: Integer; theDialog: DialogPtr; myDataPtr: Ptr):
Integer;
VAR
myType: Integer; {menu item selected}
myHandle: Handle; {needed for GetDItem}
myRect: Rect; {needed for GetDItem}
myIgnore: Integer; {needed for GetDItem; ignored}
CONST
kMyPopUpItem = 10; {item number of File Type pop-up menu}
BEGIN
MyDlgHook := item; {by default, return the item passed in}
IF GetWRefCon(WindowPtr(theDialog)) <> LongInt(sfMainDialogRefCon) THEN
Exit(MyDlgHook); {this function is only for main dialog}
{Do processing of pseudo-items and your own additional item.}
CASE item OF
sfHookFirstCall: {pseudo-item: first time function called}
BEGIN
GetDItem(theDialog, kPopUpItem, myType, myHandle, myRect);
SetCtlValue(ControlHandle(myHandle), gCurrentType);
MyDlgHook := sfHookNullEvent;
END;
kMyPopUpItem: {user selected File Type pop-up menu}
BEGIN
GetDItem(theDialog, item, myIgnore, myHandle, myRect);
myType := GetCtlValue(ControlHandle(myHandle));
IF myType <> gCurrentType THEN
BEGIN
gCurrentType := myType;
MyDlgHook := sfHookRebuildList;
END;
END;
OTHERWISE
; {ignore all other items}
END;
END;
The pop-up menu is stored as a control in the application's resource fork. Values stored in the resource determine the appearance of the control, such as the pop-up title text and the menu associated with the control. The Dialog Manager's ModalDialog procedure takes care of drawing the box around the pop-up menu and the title of the dialog box. When the dialog hook function is first called, it simply retrieves a handle to that control and sets the value of the pop-up control to the current menu item (stored in the global variable gCurrentType ). The MyDlgHook function then returns sfHookNullEvent to indicate that no further processing is required.
When the user clicks the pop-up menu control, ModalDialog calls the standard control definition function associated with it. If the user makes a selection in the pop-up menu, MyDlgHook is called with the item parameter equal to kPopUpItem. Your dialog hook function needs simply to determine the current value of the control and respond accordingly. In this case, if the user has selected a new file type, the global variable gCurrentType is updated to reflect the new selection, and MyDlgHook returns sfHookRebuildList to cause the Standard File Package to rebuild the list of files and folders displayed in the dialog box.
For complete details on handling pop-up menus, see the chapters "Control Manager" and "Menu Manager" in Inside Macintosh: Macintosh Toolbox Essentials .
A modal-dialog filter function controls events closer to their source by filtering the events received from the Event Manager. The Standard File Package itself contains an internal modal-dialog filter function that maps keypresses and other user input onto the equivalent dialog box items. If you also want to process events at this level, you can supply your own filter function.
You can supply a modal-dialog filter function only when you use one of the procedures that displays a customized dialog box (that is, CustomGetFile , CustomPutFile , SFPGetFile , or SFPPutFile ).
Your modal-dialog filter function determines how the Dialog Manager procedure ModalDialog filters events. The ModalDialog procedure retrieves events by calling the Event Manager function GetNextEvent . As just indicated, the Standard File Package contains an internal filter function that performs some preliminary processing on each event it receives. If you provide a modal-dialog filter function, ModalDialog calls your filter function after it calls the internal Standard File Package filter function and before it sends the event to your dialog hook function.
You might provide a modal-dialog filter function for several reasons. If you have customized the Open or Save dialog boxes by adding one or more items, you might want to map some of the user's keypresses to those items in the same way that the internal filter function maps certain keypresses to existing items.
Another reason to provide a modal-dialog filter function is to avoid a problem that can arise if an update event is received for one of your application's windows while a Standard File Package dialog box is displayed.
The problem described in the following paragraph occurs only in system software versions earlier than version 7.0. The internal modal-dialog filter function installed by the Standard File Package when running in version 7.0 and later avoids the problem by passing the update event to your dialog filter and, if your filter doesn't handle the event, mapping it to a null event.
When ModalDialog calls GetNextEvent and receives the update event, ModalDialog does not know how to respond to it and therefore passes the update event to the Standard File Package's internal filter function. The internal filter function cannot handle the update event either. As a result, if you do not provide your own modal-dialog filter function that handles the update event, that event is never cleared. The next time ModalDialog calls GetNextEvent , it receives the same update event. ModalDialog never receives a null event, so your dialog hook function never performs any processing in response to the sfHookNullEvent pseudo-item. You can solve this problem by providing a modal-dialog filter function that handles the update event or changes it to a null event. See Listing 3-10 for details.
A modal-dialog filter function used with SFPGetFile and SFPPutFile is declared like any filter function passed to ModalDialog . Your function is passed a pointer to the dialog record, a pointer to the event record, and the item number. (The modal-dialog filter function is described in the chapter "Dialog Manager" in Inside Macintosh: Macintosh Toolbox Essentials .)
FUNCTION MyModalFilter (theDialog: DialogPtr;
VAR theEvent: EventRecord;
VAR itemHit: Integer): Boolean;
The modal-dialog filter function used with CustomGetFile and CustomPutFile requires an additional parameter, a pointer ( myDataPtr ) to the data received from your application, if any.
FUNCTION MyModalFilterYD (theDialog: DialogPtr;
VAR theEvent: EventRecord;
VAR itemHit: Integer;
myDataPtr: Ptr): Boolean;
Your modal-dialog filter function returns a Boolean value that reports whether it handled the event. If your function returns a value of FALSE , ModalDialog processes the event through its own filters. If your function returns a value of TRUE , ModalDialog returns with no further action.
The CustomGetFile and CustomPutFile procedures call your filter function to process events in both the main dialog box and any subsidiary dialog boxes (such as the dialog box for naming a new folder while saving a document through CustomPutFile ). To determine whether the dialog record describes the main dialog box or a subsidiary dialog box, check the value of the refCon field in the window record in the dialog record, as described in "Writing a Dialog Hook Function" beginning on Writing a Dialog Hook Function .
Listing 3-10 shows how to define a modal-dialog filter function that prevents update events from clogging the event queue.
Listing 10 A sample modal-dialog filter function
FUNCTION MyModalFilter (theDialog: DialogPtr; VAR theEvent: EventRecord;
VAR itemHit: Integer): Boolean;
BEGIN
MyModalFilter := FALSE; {we haven't handled the event yet}
IF theEvent.what = updateEvt THEN
IF IsAppWindow(WindowPtr(theEvent.message)) THEN
BEGIN
DoUpdateEvent(WindowPtr(theEvent.message));
MyModalFilter := TRUE; {we have handled the event}
END;
END;
If this filter function receives an update event for a window other than the Standard File Package dialog box, it calls the application's routine for handling update events ( DoUpdateEvent ) and returns TRUE to indicate that the event has been handled. See the chapters "Event Manager" and "Window Manager" in Inside Macintosh: Macintosh Toolbox Essentials for complete details on handling update events.
The activation procedure controls the highlighting of dialog items that are defined by your application and can receive keyboard input. Ordinarily, you need to supply an activation procedure only if your application builds a list from which the user can select entries. The Standard File Package supplies the activation procedure for the file display list and for all TextEdit fields. You can also use the activation procedure to keep track of which field is receiving keyboard input, if your application needs that information.
The target of keyboard input is called the active field. The two standard keyboard-input fields are the filename field (present only in Save dialog boxes) and the display list. Unless you override it through your own dialog hook function, the Standard File Package handles the highlighting of its own items and TextEdit fields. When the user changes the keyboard target by pressing the mouse button or the Tab key, the Standard File Package calls your activation procedure twice: the first call specifies which field is being deactivated, and the second specifies which field is being activated. Your application is responsible for removing the highlighting when one of its fields becomes inactive and for adding the highlighting when one of its fields becomes active. The Standard File Package can handle the highlighting of all TextEdit fields, even those defined by your application.
The activation procedure receives four parameters: a dialog pointer, a dialog item number, a Boolean value that specifies whether the field is being activated ( TRUE ) or deactivated ( FALSE ), and a pointer to your own data.
PROCEDURE MyActivateProc (theDialog: DialogPtr; itemNo: Integer;
activating: Boolean; myDataPtr: Ptr);